Implement manifest profiles
authorAlex Crichton <alex@alexcrichton.com>
Wed, 3 Sep 2014 18:34:26 +0000 (11:34 -0700)
committerAlex Crichton <alex@alexcrichton.com>
Wed, 3 Sep 2014 18:54:29 +0000 (11:54 -0700)
For documentation, see the included documentation in the commit.

src/cargo/ops/cargo_rustc/mod.rs
src/cargo/util/toml.rs
src/doc/source/manifest.md
tests/test_cargo_compile.rs
tests/test_cargo_cross_compile.rs
tests/test_cargo_profiles.rs [new file with mode: 0644]
tests/tests.rs

index 61f19df974e2eed6ad44a856076f3935c323e09d..8d342fa7d78a319f4440eb6e66dd4d140c6f0f41 100644 (file)
@@ -242,7 +242,7 @@ fn prepare_rustc(package: &Package, target: &Target, crate_types: Vec<&str>,
                  cx: &Context, req: PlatformRequirement)
                  -> CargoResult<Vec<(ProcessBuilder, Kind)>> {
     let base = process("rustc", package, cx);
-    let base = build_base_args(base, target, crate_types.as_slice());
+    let base = build_base_args(cx, base, target, crate_types.as_slice());
 
     let target_cmd = build_plugin_args(base.clone(), cx, package, target, KindTarget);
     let plugin_cmd = build_plugin_args(base, cx, package, target, KindPlugin);
@@ -303,7 +303,7 @@ fn rustdoc(package: &Package, target: &Target,
     })
 }
 
-fn build_base_args(mut cmd: ProcessBuilder,
+fn build_base_args(cx: &Context, mut cmd: ProcessBuilder,
                    target: &Target,
                    crate_types: &[&str]) -> ProcessBuilder {
     let metadata = target.get_metadata();
@@ -317,7 +317,16 @@ fn build_base_args(mut cmd: ProcessBuilder,
         cmd = cmd.arg("--crate-type").arg(*crate_type);
     }
 
-    let profile = target.get_profile();
+    // Despite whatever this target's profile says, we need to configure it
+    // based off the profile found in the root package's targets.
+    let mut profile = target.get_profile().clone();
+    let root_package = cx.get_package(cx.resolve.root());
+    for target in root_package.get_manifest().get_targets().iter() {
+        let root_profile = target.get_profile();
+        if root_profile.get_env() != profile.get_env() { continue }
+        profile = profile.opt_level(root_profile.get_opt_level())
+                         .debug(root_profile.get_debug());
+    }
 
     if profile.get_opt_level() != 0 {
         cmd = cmd.arg("--opt-level").arg(profile.get_opt_level().to_string());
index a268e6b70bebc14f165931942ce8e03d9b1fde8a..c90d318830413cc6a8719f58df9ef18a4603de0f 100644 (file)
@@ -4,6 +4,7 @@ use std::io::fs;
 use std::os;
 use std::slice;
 use std::str;
+use std::default::Default;
 use toml;
 use semver;
 use serialize::{Decodable, Decoder};
@@ -198,6 +199,7 @@ pub struct DetailedTomlDependency {
 pub struct TomlManifest {
     package: Option<Box<TomlProject>>,
     project: Option<Box<TomlProject>>,
+    profile: Option<TomlProfiles>,
     lib: Option<ManyOrOne<TomlLibTarget>>,
     bin: Option<Vec<TomlBinTarget>>,
     example: Option<Vec<TomlExampleTarget>>,
@@ -207,6 +209,21 @@ pub struct TomlManifest {
     dev_dependencies: Option<HashMap<String, TomlDependency>>,
 }
 
+#[deriving(Decodable, Clone, Default)]
+pub struct TomlProfiles {
+    test: Option<TomlProfile>,
+    doc: Option<TomlProfile>,
+    bench: Option<TomlProfile>,
+    dev: Option<TomlProfile>,
+    release: Option<TomlProfile>,
+}
+
+#[deriving(Decodable, Clone, Default)]
+pub struct TomlProfile {
+    opt_level: Option<uint>,
+    debug: Option<bool>,
+}
+
 #[deriving(Decodable)]
 pub enum ManyOrOne<T> {
     Many(Vec<T>),
@@ -411,12 +428,14 @@ impl TomlManifest {
         };
 
         // Get targets
+        let profiles = self.profile.clone().unwrap_or(Default::default());
         let targets = normalize(lib.as_slice(),
                                 bins.as_slice(),
                                 examples.as_slice(),
                                 tests.as_slice(),
                                 benches.as_slice(),
-                                &metadata);
+                                &metadata,
+                                &profiles);
 
         if targets.is_empty() {
             debug!("manifest has no build targets");
@@ -572,38 +591,61 @@ fn normalize(libs: &[TomlLibTarget],
              examples: &[TomlExampleTarget],
              tests: &[TomlTestTarget],
              benches: &[TomlBenchTarget],
-             metadata: &Metadata) -> Vec<Target> {
+             metadata: &Metadata,
+             profiles: &TomlProfiles) -> Vec<Target> {
     log!(4, "normalizing toml targets; lib={}; bin={}; example={}; test={}, benches={}",
          libs, bins, examples, tests, benches);
 
     enum TestDep { Needed, NotNeeded }
 
-    fn target_profiles(target: &TomlTarget, dep: TestDep) -> Vec<Profile> {
-        let mut ret = vec![Profile::default_dev(), Profile::default_release()];
+    fn merge(profile: Profile, toml: &Option<TomlProfile>) -> Profile {
+        let toml = match *toml {
+            Some(ref toml) => toml,
+            None => return profile,
+        };
+        let opt_level = toml.opt_level.unwrap_or(profile.get_opt_level());
+        let debug = toml.debug.unwrap_or(profile.get_debug());
+        profile.opt_level(opt_level).debug(debug)
+    }
+
+    fn target_profiles(target: &TomlTarget, profiles: &TomlProfiles,
+                       dep: TestDep) -> Vec<Profile> {
+        let mut ret = vec![
+            merge(Profile::default_dev(), &profiles.dev),
+            merge(Profile::default_release(), &profiles.release),
+        ];
 
         match target.test {
-            Some(true) | None => ret.push(Profile::default_test()),
+            Some(true) | None => {
+                ret.push(merge(Profile::default_test(), &profiles.test));
+            }
             Some(false) => {}
         }
 
         let doctest = target.doctest.unwrap_or(true);
         match target.doc {
             Some(true) | None => {
-                ret.push(Profile::default_doc().doctest(doctest));
+                ret.push(merge(Profile::default_doc().doctest(doctest),
+                               &profiles.doc));
             }
             Some(false) => {}
         }
 
         match target.bench {
-            Some(true) | None => ret.push(Profile::default_bench()),
+            Some(true) | None => {
+                ret.push(merge(Profile::default_bench(), &profiles.bench));
+            }
             Some(false) => {}
         }
 
         match dep {
             Needed => {
-                ret.push(Profile::default_test().test(false));
-                ret.push(Profile::default_doc().doc(false));
-                ret.push(Profile::default_bench().test(false));
+                ret.push(merge(Profile::default_test().test(false),
+                               &profiles.test));
+                ret.push(merge(Profile::default_doc().doc(false),
+                               &profiles.doc));
+                ret.push(merge(Profile::default_bench().test(false),
+                               &profiles.bench));
             }
             _ => {}
         }
@@ -616,7 +658,7 @@ fn normalize(libs: &[TomlLibTarget],
     }
 
     fn lib_targets(dst: &mut Vec<Target>, libs: &[TomlLibTarget],
-                   dep: TestDep, metadata: &Metadata) {
+                   dep: TestDep, metadata: &Metadata, profiles: &TomlProfiles) {
         let l = &libs[0];
         let path = l.path.clone().unwrap_or_else(|| {
             TomlString(format!("src/{}.rs", l.name))
@@ -627,7 +669,7 @@ fn normalize(libs: &[TomlLibTarget],
             vec![if l.plugin == Some(true) {Dylib} else {Lib}]
         });
 
-        for profile in target_profiles(l, dep).iter() {
+        for profile in target_profiles(l, profiles, dep).iter() {
             let mut metadata = metadata.clone();
             // Libs and their tests are built in parallel, so we need to make
             // sure that their metadata is different.
@@ -641,14 +683,14 @@ fn normalize(libs: &[TomlLibTarget],
     }
 
     fn bin_targets(dst: &mut Vec<Target>, bins: &[TomlBinTarget],
-                   dep: TestDep, metadata: &Metadata,
+                   dep: TestDep, metadata: &Metadata, profiles: &TomlProfiles,
                    default: |&TomlBinTarget| -> String) {
         for bin in bins.iter() {
             let path = bin.path.clone().unwrap_or_else(|| {
                 TomlString(default(bin))
             });
 
-            for profile in target_profiles(bin, dep).iter() {
+            for profile in target_profiles(bin, profiles, dep).iter() {
                 let metadata = if profile.is_test() {
                     // Make sure that the name of this test executable doesn't
                     // conflicts with a library that has the same name and is
@@ -668,19 +710,21 @@ fn normalize(libs: &[TomlLibTarget],
     }
 
     fn example_targets(dst: &mut Vec<Target>, examples: &[TomlExampleTarget],
+                       profiles: &TomlProfiles,
                        default: |&TomlExampleTarget| -> String) {
         for ex in examples.iter() {
             let path = ex.path.clone().unwrap_or_else(|| TomlString(default(ex)));
 
-            let profile = &Profile::default_test().test(false);
+            let profile = Profile::default_test().test(false);
+            let profile = merge(profile, &profiles.test);
             dst.push(Target::example_target(ex.name.as_slice(),
                                             &path.to_path(),
-                                            profile));
+                                            &profile));
         }
     }
 
     fn test_targets(dst: &mut Vec<Target>, tests: &[TomlTestTarget],
-                    metadata: &Metadata,
+                    metadata: &Metadata, profiles: &TomlProfiles,
                     default: |&TomlTestTarget| -> String) {
         for test in tests.iter() {
             let path = test.path.clone().unwrap_or_else(|| {
@@ -692,16 +736,17 @@ fn normalize(libs: &[TomlLibTarget],
             let mut metadata = metadata.clone();
             metadata.mix(&format!("test-{}", test.name));
 
-            let profile = &Profile::default_test().harness(harness);
+            let profile = Profile::default_test().harness(harness);
+            let profile = merge(profile, &profiles.test);
             dst.push(Target::test_target(test.name.as_slice(),
                                          &path.to_path(),
-                                         profile,
+                                         &profile,
                                          metadata));
         }
     }
 
     fn bench_targets(dst: &mut Vec<Target>, benches: &[TomlBenchTarget],
-                     metadata: &Metadata,
+                     metadata: &Metadata, profiles: &TomlProfiles,
                      default: |&TomlBenchTarget| -> String) {
         for bench in benches.iter() {
             let path = bench.path.clone().unwrap_or_else(|| {
@@ -713,11 +758,12 @@ fn normalize(libs: &[TomlLibTarget],
             let mut metadata = metadata.clone();
             metadata.mix(&format!("bench-{}", bench.name));
 
-            let profile = &Profile::default_bench().harness(harness);
+            let profile = Profile::default_bench().harness(harness);
+            let profile = merge(profile, &profiles.bench);
             dst.push(Target::bench_target(bench.name.as_slice(),
-                                         &path.to_path(),
-                                         profile,
-                                         metadata));
+                                          &path.to_path(),
+                                          &profile,
+                                          metadata));
         }
     }
 
@@ -731,25 +777,25 @@ fn normalize(libs: &[TomlLibTarget],
 
     match (libs, bins) {
         ([_, ..], [_, ..]) => {
-            lib_targets(&mut ret, libs, Needed, metadata);
-            bin_targets(&mut ret, bins, test_dep, metadata,
+            lib_targets(&mut ret, libs, Needed, metadata, profiles);
+            bin_targets(&mut ret, bins, test_dep, metadata, profiles,
                         |bin| format!("src/bin/{}.rs", bin.name));
         },
         ([_, ..], []) => {
-            lib_targets(&mut ret, libs, Needed, metadata);
+            lib_targets(&mut ret, libs, Needed, metadata, profiles);
         },
         ([], [_, ..]) => {
-            bin_targets(&mut ret, bins, test_dep, metadata,
+            bin_targets(&mut ret, bins, test_dep, metadata, profiles,
                         |bin| format!("src/{}.rs", bin.name));
         },
         ([], []) => ()
     }
 
 
-    example_targets(&mut ret, examples,
+    example_targets(&mut ret, examples, profiles,
                     |ex| format!("examples/{}.rs", ex.name));
 
-    test_targets(&mut ret, tests, metadata,
+    test_targets(&mut ret, tests, metadata, profiles,
                 |test| {
                     if test.name.as_slice() == "test" {
                         "src/test.rs".to_string()
@@ -757,7 +803,7 @@ fn normalize(libs: &[TomlLibTarget],
                         format!("tests/{}.rs", test.name)
                     }});
 
-    bench_targets(&mut ret, benches, metadata,
+    bench_targets(&mut ret, benches, metadata, profiles,
                  |bench| {
                      if bench.name.as_slice() == "bench" {
                          "src/bench.rs".to_string()
index c000066a0f43d507b4bcfe511812e65aad6dc06a..9e70b598801cef81f2d351c74d26e3cd66797490 100644 (file)
@@ -93,6 +93,45 @@ You can specify the source of a dependency in one of two ways at the moment:
 
 Soon, you will be able to load packages from the Cargo registry as well.
 
+# The `[profile.*]` Sections
+
+Cargo supports custom configuration of how rustc is invoked through **profiles**
+at the top level. Any manifest may declare a profile, but only the **top level**
+project's profiles are actually read. All dependencies' profiles will be
+overridden. This is done so the top-level project has control over how its
+dependencies are compiled.
+
+There are five currently supported profile names, all of which have the same
+configuration available to them. Listed below is the configuration available,
+along with the defaults for each profile.
+
+```toml
+# The development profile, used for `cargo build`
+[profile.dev]
+opt-level = 0  # Controls the --opt-level the compiler builds with
+debug = true   # Controls whether the compiler passes -g or `--cfg ndebug`
+
+# The release profile, used for `cargo build --release`
+[profile.release]
+opt-level = 3
+debug = false
+
+# The testing profile, used for `cargo test`
+[profile.test]
+opt-level = 0
+debug = true
+
+# The benchmarking profile, used for `cargo bench`
+[profile.bench]
+opt-level = 3
+debug = false
+
+# The documentation profile, used for `cargo doc`
+[profile.doc]
+opt-level = 0
+debug = true
+```
+
 # The `[dev-dependencies.*]` Sections
 
 The format of this section is equivalent to `[dependencies.*]`. Dev-dependencies
index cb32c1babbc3b969504e14590a7b97d6803bbf04..ace545bc2eeff844ea02e5938236a94619f2c05c 100644 (file)
@@ -1153,7 +1153,7 @@ test!(verbose_build {
         .file("src/lib.rs", "");
     assert_that(p.cargo_process("build").arg("-v"),
                 execs().with_status(0).with_stdout(format!("\
-{running} `rustc {dir}{sep}src{sep}lib.rs --crate-name test --crate-type lib \
+{running} `rustc {dir}{sep}src{sep}lib.rs --crate-name test --crate-type lib -g \
         -C metadata=[..] \
         -C extra-filename=-[..] \
         --out-dir {dir}{sep}target \
index ab5c2ec5915ff0be6180020dc40e869991504302..13c1c6663a414b19b3753c3413c0e8925c176d3f 100644 (file)
@@ -295,7 +295,7 @@ test!(linker_and_ar {
                                               .arg("-v"),
                 execs().with_status(101)
                        .with_stdout(format!("\
-{running} `rustc src/foo.rs --crate-name foo --crate-type bin \
+{running} `rustc src/foo.rs --crate-name foo --crate-type bin -g \
     --out-dir {dir}{sep}target{sep}{target} \
     --dep-info [..] \
     --target {target} \
diff --git a/tests/test_cargo_profiles.rs b/tests/test_cargo_profiles.rs
new file mode 100644 (file)
index 0000000..e51ad49
--- /dev/null
@@ -0,0 +1,111 @@
+use std::os;
+use std::path;
+
+use support::{project, execs};
+use support::{COMPILING, RUNNING};
+use hamcrest::assert_that;
+
+fn setup() {
+}
+
+test!(profile_overrides {
+    let mut p = project("foo");
+    p = p
+        .file("Cargo.toml", r#"
+            [package]
+
+            name = "test"
+            version = "0.0.0"
+            authors = []
+
+            [profile.dev]
+            opt-level = 1
+            debug = false
+        "#)
+        .file("src/lib.rs", "");
+    assert_that(p.cargo_process("build").arg("-v"),
+                execs().with_status(0).with_stdout(format!("\
+{running} `rustc {dir}{sep}src{sep}lib.rs --crate-name test --crate-type lib \
+        --opt-level 1 \
+        --cfg ndebug \
+        -C metadata=[..] \
+        -C extra-filename=-[..] \
+        --out-dir {dir}{sep}target \
+        --dep-info [..] \
+        -L {dir}{sep}target \
+        -L {dir}{sep}target{sep}deps`
+{compiling} test v0.0.0 ({url})\n",
+running = RUNNING, compiling = COMPILING, sep = path::SEP,
+dir = p.root().display(),
+url = p.url(),
+)));
+})
+
+test!(top_level_overrides_deps {
+    let mut p = project("foo");
+    p = p
+        .file("Cargo.toml", r#"
+            [package]
+
+            name = "test"
+            version = "0.0.0"
+            authors = []
+
+            [profile.release]
+            opt-level = 1
+            debug = true
+
+            [dependencies.foo]
+            path = "foo"
+        "#)
+        .file("src/lib.rs", "")
+        .file("foo/Cargo.toml", r#"
+            [package]
+
+            name = "foo"
+            version = "0.0.0"
+            authors = []
+
+            [profile.release]
+            opt-level = 0
+            debug = false
+
+            [lib]
+            name = "foo"
+            crate_type = ["dylib", "rlib"]
+        "#)
+        .file("foo/src/lib.rs", "");
+    assert_that(p.cargo_process("build").arg("-v").arg("--release"),
+                execs().with_status(0).with_stdout(format!("\
+{running} `rustc {dir}{sep}foo{sep}src{sep}lib.rs --crate-name foo \
+        --crate-type dylib --crate-type rlib \
+        --opt-level 1 \
+        -g \
+        -C metadata=[..] \
+        -C extra-filename=-[..] \
+        --out-dir {dir}{sep}target{sep}release{sep}deps \
+        --dep-info [..] \
+        -L {dir}{sep}target{sep}release{sep}deps \
+        -L {dir}{sep}target{sep}release{sep}deps`
+{running} `rustc {dir}{sep}src{sep}lib.rs --crate-name test --crate-type lib \
+        --opt-level 1 \
+        -g \
+        -C metadata=[..] \
+        -C extra-filename=-[..] \
+        --out-dir {dir}{sep}target{sep}release \
+        --dep-info [..] \
+        -L {dir}{sep}target{sep}release \
+        -L {dir}{sep}target{sep}release{sep}deps \
+        --extern foo={dir}{sep}target{sep}release{sep}deps/\
+                     {prefix}foo-[..]{suffix} \
+        --extern foo={dir}{sep}target{sep}release{sep}deps/libfoo-[..].rlib`
+{compiling} foo v0.0.0 ({url})
+{compiling} test v0.0.0 ({url})\n",
+                    running = RUNNING,
+                    compiling = COMPILING,
+                    dir = p.root().display(),
+                    url = p.url(),
+                    sep = path::SEP,
+                    prefix = os::consts::DLL_PREFIX,
+                    suffix = os::consts::DLL_SUFFIX).as_slice()));
+})
index c84e206338be92c71f7003b1bdaeec93b5f7904e..95ef993156939a513c6c129908ec66c5877fc0e3 100644 (file)
@@ -37,4 +37,5 @@ mod test_cargo_compile_plugins;
 mod test_cargo_doc;
 mod test_cargo_freshness;
 mod test_cargo_generate_lockfile;
+mod test_cargo_profiles;
 mod test_cargo_package;